#region Using References

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Linq;
using Beaker.OpenCube.Resources;

#endregion

namespace Beaker.OpenCube.Textures
{
	/// <summary>
	/// Single Texture
	/// </summary>
	[DebuggerDisplay( "Texture( Resolution = {Resolution}x{Resolution}, Images = {ImageCount}, Frames = {FrameCount} )" )]
	public sealed class Texture
	{
		private readonly Bitmap _image;
		private readonly int _imageCount;
		private readonly int _frameCount;
		private readonly FrameInfo[] _frameInfo;
		private readonly IReadOnlyList<FrameInfo> _frameInformation;
		private readonly int[] _frameIndexer;

		[Pure]
		[DebuggerStepThrough]
		[EditorBrowsable( EditorBrowsableState.Never )]
		public static bool IsPowerOfTwo( uint x )
		{
			Contract.Requires<ArgumentOutOfRangeException>( x != 0, "Edgecase of 0 not supported" );

			// http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2
			return ( x & ( x - 1 ) ) == 0;
		}

		public Texture( Bitmap image, TextureMetadata metadata )
			: this( image, MetadataToFrameInfo( image, metadata ) )
		{
		}

		[Pure]
		private static IEnumerable<FrameInfo> MetadataToFrameInfo( Bitmap image, TextureMetadata metadata )
		{
			Contract.Requires<ArgumentNullException>( image != null );
			
			if( metadata == null || metadata.Animation == null )
				return null;

			int imageCount = image.Height / image.Width;
			int defaultRepeatCount = metadata.Animation.FrameTime ?? 1;

			if( metadata.Animation.Frames == null )
				return CreateFrameInfo( imageCount, defaultRepeatCount ).ToArray();

			var frameInfo = new List<FrameInfo>();
			foreach( TextureFrameMetadata frameMetadata in metadata.Animation.Frames )
				frameInfo.Add( new FrameInfo( frameMetadata.Index, frameMetadata.Time ?? defaultRepeatCount ) );
			return frameInfo;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Texture" /> class.
		/// </summary>
		/// <param name="image">The image.</param>
		/// <param name="frameInfo">The frame information, if any.</param>
		/// <exception cref="System.InvalidOperationException">Thrown when the number of frames in the frame information does not match the number of frames in the texture image</exception>
		public Texture( Bitmap image, IEnumerable<FrameInfo> frameInfo = null )
		{
			Contract.Requires<ArgumentNullException>( image != null );
			Contract.Requires<ArgumentException>( image.Width > 0, "image has invalid resolution" );
			Contract.Requires<ArgumentException>( IsPowerOfTwo( (uint)image.Width ), "image has invalid resolution" );
			Contract.Requires<ArgumentException>( image.Height % image.Width == 0, "image has invalid resolution" );
			Contract.Ensures( Resolution > 0 );
			Contract.Ensures( IsPowerOfTwo( (uint)Resolution ) );
			Contract.Ensures( ImageCount > 0 );
			Contract.Ensures( FrameInformation != null );

			_image = image;
			_imageCount = _image.Height / _image.Width;
			_frameInfo =
				frameInfo == null
					? CreateFrameInfo( _imageCount ).ToArray()
					: frameInfo.ToArray();
			_frameInformation = new ReadOnlyCollection<FrameInfo>( _frameInfo );

			// compute total frame count
			_frameCount = _frameInfo.Sum( fi => fi.FrameRepeatCount );
			// create expanded frame indexing array
			_frameIndexer = _frameInfo.SelectMany( fi => Enumerable.Repeat( fi.FrameIndex, fi.FrameRepeatCount ) ).ToArray();
		}

		[Pure]
		private static IEnumerable<FrameInfo> CreateFrameInfo( int count, int defaultRepeatCount = 1 )
		{
			for( int i = 0; i < count; i++ )
				yield return new FrameInfo( i, defaultRepeatCount );
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _image != null );
			Contract.Invariant( _image.Width > 0 );
			Contract.Invariant( _imageCount > 0 );
			Contract.Invariant( _frameInfo != null );
		}

		/// <summary>
		/// Gets the resolution.
		/// </summary>
		/// <value>
		/// The resolution.
		/// </value>
		public int Resolution
		{
			get
			{
				Contract.Ensures( Contract.Result<int>() > 0 );
				Contract.Ensures( IsPowerOfTwo( (uint)Contract.Result<int>() ) );

				return _image.Width;
			}
		}

		/// <summary>
		/// Gets the number images in the texture.
		/// </summary>
		/// <value>
		/// The number of images in the texture.
		/// </value>
		public int ImageCount
		{
			get
			{
				Contract.Ensures( Contract.Result<int>() > 0 );

				return _imageCount;
			}
		}

		/// <summary>
		/// Gets the total frame count.
		/// </summary>
		/// <value>
		/// The total frame count.
		/// </value>
		public int FrameCount
		{
			get { return _frameCount; }
		}

		/// <summary>
		/// Gets the list of frame information.
		/// </summary>
		/// <value>
		/// The list of framei information.
		/// </value>
		public IReadOnlyList<FrameInfo> FrameInformation
		{
			get { return _frameInformation; }
		}

		/// <summary>
		/// Gets the image of the texture.
		/// </summary>
		/// <value>
		/// The image of the texture.
		/// </value>
		public Bitmap Image
		{
			get { return _image; }
		}

		/// <summary>
		/// Gets the rectangle for the given image index.
		/// </summary>
		/// <param name="imageIndex">Index of the image index.</param>
		/// <returns>Rectangle of the part of the image for the given image index.</returns>
		public Rectangle GetRectangleForImageIndex( int imageIndex )
		{
			Contract.Requires<ArgumentOutOfRangeException>( imageIndex >= 0 );
			Contract.Requires<ArgumentOutOfRangeException>( imageIndex < ImageCount );
			Contract.Ensures( Contract.Result<Rectangle>().Width == Resolution );
			Contract.Ensures( Contract.Result<Rectangle>().Height == Resolution );

			int r = Resolution;
			return new Rectangle( 0, r * imageIndex, r, r );
		}

		/// <summary>
		/// Gets the rectangle for the given frame number.
		/// </summary>
		/// <param name="frameNumber">Number of the frame.</param>
		/// <returns>Rectangle of the part of the image for the given frame number.</returns>
		public Rectangle GetRectangleForFrame( int frameNumber )
		{
			Contract.Requires<ArgumentOutOfRangeException>( frameNumber >= 0 );
			Contract.Requires<ArgumentOutOfRangeException>( frameNumber < FrameCount );
			Contract.Ensures( Contract.Result<Rectangle>().Width == Resolution );
			Contract.Ensures( Contract.Result<Rectangle>().Height == Resolution );

			int r = Resolution;
			return new Rectangle( 0, r * _frameIndexer[frameNumber], r, r );
		}
	}
}